# _*_ coding: utf-8 _*_
import json
import logging
import threading
import requests
from lxml import html

from odoo import models, fields, api, _
from odoo.exceptions import UserError, AccessError
from odoo.tools.rendering_tools import convert_inline_template_to_qweb, parse_inline_template, render_inline_template, template_env_globals

_logger = logging.getLogger(__name__)


class FKCPortalMessageTemplate(models.Model):
    _name = "fkc.portal.message.template"
    _description = 'FKC Portal Message Templates'
    _order = "sequence, id"

    _unrestricted_rendering = False

    name = fields.Char('Name', translate=False)
    sequence = fields.Integer(default=10)
    model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with")
    model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
    partner_to_ids = fields.Many2many("res.partner", string='To (Partners)')
    engine = fields.Selection([
        ("inline_template", "Inline Template"),
    ], default="inline_template", required=True)
    template = fields.Text()

    @api.model
    def _render_eval_context(self):
        """ Evaluation context used in all rendering engines. Contains

          * ``user``: current user browse record;
          * ``ctx```: current context;
          * various formatting tools;
        """
        render_context = self.env["mail.render.mixin"]._render_eval_context()
        return render_context

    @api.model
    def _render_template_inline_template(self, template_txt, model, res_ids,
                                         add_context=None, options=None):
        """ Render a string-based template on records given by a model and a list
        of IDs, using inline_template.

        In addition to the generic evaluation context available, some other
        variables are added:
          * ``object``: record based on which the template is rendered;

        :param str template_txt: template text to render
        :param str model: see ``MailRenderMixin._render_template()``;
        :param list res_ids: see ``MailRenderMixin._render_template()``;

        :param dict add_context: additional context to give to renderer. It
          allows to add or update values to base rendering context generated
          by ``MailRenderMixin._render_inline_template_eval_context()``;
        :param dict options: options for rendering;

        :return str: string of rendered template based on records
        """
        # prevent wrong values (rendering on a void record set, ...)
        if any(r is None for r in res_ids):
            raise ValueError(_('Template rendering should be called on a valid record IDs.'))

        if not template_txt:
            return ""

        template_instructions = parse_inline_template(str(template_txt))
        is_dynamic = len(template_instructions) > 1 or template_instructions[0][1]

        if (not self._unrestricted_rendering and is_dynamic and not self.env.is_admin() and
           not self.env.user.has_group('mail.group_mail_template_editor')):
            group = self.env.ref('mail.group_mail_template_editor')
            raise AccessError(_('Only users belonging to the "%s" group can modify dynamic templates.', group.name))

        if not is_dynamic:
            # Either the content is a raw text without placeholders, either we fail to
            # detect placeholders code. In both case we skip the rendering and return
            # the raw content, so even if we failed to detect dynamic code,
            # non "mail_template_editor" users will not gain rendering tools available
            # only for template specific group users
            return template_instructions[0][0]

        # prepare template variables
        variables = self._render_eval_context()
        if add_context:
            variables.update(**add_context)

        variables['objects'] = self.env[model].browse(res_ids)

        try:
            return render_inline_template(template_instructions, variables)
        except Exception as e:
            _logger.info("Failed to render inline_template: \n%s", str(template_txt), exc_info=True)
            raise UserError(_("Failed to render inline_template template : %s)", e))

    @api.model
    def _render_template(self, template_src, model, res_ids, engine='inline_template',
                         add_context=None, options=None):
        """ Render the given string on records designed by model / res_ids using
        the given rendering engine. Possible engine are small_web, qweb, or
        qweb_view.

        :param str template_src: template text to render or xml id of a qweb view;
        :param str model: model name of records on which we want to perform
          rendering (aka 'crm.lead');
        :param list res_ids: list of ids of records. All should belong to the
          Odoo model given by model;
        :param string engine: inline_template, qweb or qweb_view;

        :param dict add_context: additional context to give to renderer. It
          allows to add or update values to base rendering context generated
          by ``MailRenderMixin._render_<engine>_eval_context()``;
        :param dict options: options for rendering;


        :return str: string of rendered template based on records
        """
        if not isinstance(res_ids, (list, tuple)):
            raise ValueError(_('Template rendering should be called only using on a list of IDs.'))

        if engine not in ('inline_template', 'qweb'):
            raise ValueError(_('Template rendering supports only inline_template, qweb.'))

        return getattr(self, f"_render_template_{engine}")(template_src, model, res_ids,
                                                           add_context=add_context, options=options)

    def send_message(self, res_ids, force_send=False, raise_exception=False):
        self.ensure_one()
        message = self.env["fkc.portal.message"].create({
            "name": self.name,
            "template_id": self.id,
            "res_ids": repr(res_ids),  # literal_eval 可以使用这个还原list
            "body": self._render_template(self.template, self.model, res_ids, engine=self.engine,
                                          add_context={})
        })
        if force_send:
            message.send(raise_exception=raise_exception)


class FKCPortalMessage(models.Model):
    _name = "fkc.portal.message"
    _order = 'id desc'
    _description = 'FKC Portal Message'

    # description
    name = fields.Char('Name', translate=True)
    state = fields.Selection([
        ('outgoing', 'Outgoing'),
        ('sent', 'Sent'),
        ('exception', 'Exception'),
        ('cancel', 'Cancelled'),
    ], 'Status', readonly=True, copy=False, default='outgoing')
    template_id = fields.Many2one("fkc.portal.message.template")
    model_id = fields.Many2one(related='template_id.model_id', string='Applies to',
                               help="The type of document this template can be used with")
    model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True)
    res_ids = fields.Char('Document IDs')
    scheduled_date = fields.Datetime('Scheduled Send Date', default=fields.Datetime.now,
                                     help="If set, the queue manager will send the message after the date. "
                                          "If not set, the email will be send as soon as possible.")
    body = fields.Text()
    failure_reason = fields.Text(
        'Failure Reason', readonly=True, copy=False,
        help="Failure reason. This is usually the exception from wecom, stored to ease the debugging of issues.")

    @api.model
    def process_email_queue(self):
        # better make limit configurable
        messages = self.search([('state', '=', 'outgoing')], limit=10000)

        res = None
        try:
            # auto-commit except in testing mode
            auto_commit = not getattr(threading.currentThread(), 'testing', False)
            res = messages.send(auto_commit=auto_commit)
        except Exception:
            _logger.exception("Failed processing fkc portal message queue.")
        return res

    def send(self, auto_commit=False, raise_exception=False):
        ICP = self.env['ir.config_parameter'].sudo()
        service_key = ICP.get_param('fkc.portal.service.key')
        url = ICP.get_param('fkc.portal.url')
        for msg in self:
            try:
                headers = {
                    'content-type': 'application/json',
                    'ServiceKey': service_key,
                }
                res = requests.post(url, json=msg.body.encode(), headers=headers).json()
                if res['status'] == 0:
                    msg.state = "sent"
                    msg.failure_reason = res['message']
                else:
                    msg.write({
                        "state": "exception",
                        "failure_reason": res['message'],
                    })
                    raise UserError(msg.failure_reason)
            except Exception as ex:
                msg.write({
                    "state": "exception",
                    "failure_reason": str(ex),
                })
                if raise_exception:
                    raise
            if auto_commit:
                self.env._cr.commit()

    def action_retry(self):
        self.filtered(lambda message: message.state == 'exception').mark_outgoing()

    def mark_outgoing(self):
        return self.write({'state': 'outgoing'})

    def cancel(self):
        return self.write({'state': 'cancel'})
